package gitlet;

import java.io.Console;
import java.io.File;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;

import static gitlet.Utils.*;

public class Repository implements Serializable {

    /**
     * The current working directory.
     */
    public static final File CWD = new File(System.getProperty("user.dir"));
    /**
     * The .gitlet directory.
     */
    public static final File GITLET_DIR = join(CWD, ".gitlet");
    public static final File GITLET_DIR_OBJECT = join(GITLET_DIR, "object");
    public static final File GITLET_DIR_OBJECT_BLOBS = join(GITLET_DIR_OBJECT, "blob");
    private String head;
    private StagingArea stagingArea;
    HashMap<String, String> branches;


    public Repository(String initialCommitID) {
        this.head = "master";
        this.stagingArea = new StagingArea();
        this.branches = new HashMap<>();
        this.branches.put("master", initialCommitID);
    }

    //存储唯一的repo
    public void saveRepository() {
        String location = "Kiki";
        File s = new File(GITLET_DIR, location);
        writeObject(s, this);
    }

    //从repo读文件返回repo类型
    public static Repository fromFile() {
        File f = new File(GITLET_DIR, "Kiki");
        return Utils.readObject(f, Repository.class);
    }


    public StagingArea getStagingArea() {
        return this.stagingArea;
    }

    public HashMap<String, String> getBranches() {
        return this.branches;
    }

    public void setHead(String head) {
        this.head = head;
    }

    public String getHead() {
        return this.head;
    }

    //完成add
    public int add(String fileName) {
        Commit addCommitID = Commit.fromFile(this.getBranches().get(head));
        File f = new File(Repository.CWD, fileName);
        if (!f.exists()) {
            System.out.println("File does not exist.");
            return -1;
        } else {
            Blob newBlob = new Blob(Utils.readContents(f));
            List<String> allFile = Utils.plainFilenamesIn(GITLET_DIR_OBJECT_BLOBS); //当前文件的sha1可以通过blob拿到
            String fileSha1 = newBlob.getSha1();
            if (!addCommitID.fileList.containsKey(fileName)) {
                this.getStagingArea().getAddition().put(fileName, newBlob.getSha1());
                newBlob.saveBlob();
            } else {
                if (!Objects.equals(addCommitID.fileList.get(fileName), fileSha1)) {
                    this.getStagingArea().getAddition().put(fileName, newBlob.getSha1());
                    newBlob.saveBlob();
                } else {
                    if (this.getStagingArea().getAddition().containsKey(fileName)) {
                        this.getStagingArea().getAddition().remove(fileName);
                    }
                }
            }
            if (this.getStagingArea().getRemove().contains(fileName)) {
                this.getStagingArea().getRemove().remove(fileName);
            }
            return 0;
        }


    }

    //commit
    public int makeCommit(String message, String parent2) {
        String currentBranch = this.getHead();
        String prevCommitID = this.getBranches().get(currentBranch);

        //计算当前commit的timestamp
        Date now = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.ENGLISH);
        dateFormat.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
        String newTimeStamp = dateFormat.format(now);

        //stagingarea的处理
        HashMap<String, String> filesToAdd = this.getStagingArea().getAddition();
        //检查暂存区域的文件是不是已经存在，已经存在则不需要再次上传，直接删除
        ArrayList<String> filesToRemove = this.getStagingArea().getRemove();

        if (filesToAdd.isEmpty() && filesToRemove.isEmpty()) {
            System.out.println("No changes added to the commit.");
            return -1;
        }
        //新的提交
        Commit newCommit = new Commit(message, prevCommitID, parent2, newTimeStamp, filesToAdd, filesToRemove);
        newCommit.saveCommit();


        //清空stagingArea
        this.getStagingArea().getAddition().clear();
        this.getStagingArea().getRemove().clear();
        this.getBranches().put(currentBranch, newCommit.getCommitID());//这个是makecommit
        this.saveRepository();

        return 0;
        //branch没有更新
    }

    public int remove(String fileName) {
        Commit currentCommit = Commit.fromFile(this.getBranches().get(head));
        if (!this.getStagingArea().getAddition().containsKey(fileName)
                && !currentCommit.fileList.containsKey(fileName)) {
            System.out.println("No reason to remove the file.");
            return -1;
        }
        //in staging area
        if (this.getStagingArea().getAddition().containsKey(fileName)) {
            this.getStagingArea().getAddition().remove(fileName);
        }
        //tracked by the head
        if (currentCommit.fileList.containsKey(fileName)) {
            if (!this.getStagingArea().getRemove().contains(fileName)) {
                this.getStagingArea().getRemove().add(fileName);
            }
            //remove cwd file
            File fileRm = new File(CWD, fileName);
            if (fileRm.exists()) {
                Utils.restrictedDelete(fileRm);
            }
        }
        this.saveRepository();
        return 0;
    }

    public int checkout2(String commitprefix, String fileName) {
        //java gitlet.Main checkout [commit id] -- [file name]
        String commitString = Commit.getUniqueIDIfExist(commitprefix);
        if (commitString == null) {
            System.out.println(" No commit with that id exists.");
            return -1;
        }
        File file2 = new File(GITLET_DIR_OBJECT, commitString);
        if (file2.exists()) {
            Commit commitID = Commit.fromFile(commitString);
            if (commitID.fileList.containsKey(fileName)) {
                String fileSha1 = commitID.fileList.get(fileName);
                File s = new File(CWD, fileName);
                Utils.writeContents(s, Blob.fromFile(fileSha1).getData());
            } else {
                System.out.println("File does not exist in that commit.");
                return -1;
            }
        } else {
            System.out.println("No commit with that id exists.");
            return -1;
        }
        return 0;
    }

    public int checkout3(String branchName) {
        //java gitlet.Main checkout [branch name]
        if (Objects.equals(this.getHead(), branchName)) {
            System.out.println("No need to checkout the current branch.");
            return -1;


        } else if (this.branches.containsKey(branchName)) {
            String branchSha1 = this.branches.get(branchName);
            Commit branchCommitID = Commit.fromFile(branchSha1);
            Commit headCommitID = Commit.fromFile(this.getBranches().get(this.getHead()));
            boolean errorfound = false;
            for (String fileName : branchCommitID.fileList.keySet()) {
                File s = new File(CWD, fileName);
                if (!headCommitID.fileList.containsKey(fileName)) {
                    if (s.exists()) {
                        errorfound = true;
                    }
                }
            }
            if (errorfound) {
                System.out.println("There is an untracked file in the way; delete it, "
                        + "or add and commit it first.");
                return -1;

            }
            for (HashMap.Entry<String, String> entry : branchCommitID.fileList.entrySet()) {
                File s = new File(CWD, entry.getKey());
                Utils.writeContents(s, Blob.fromFile(entry.getValue()).getData());
            }
            String currentbranchCommit = this.getBranches().get(this.getHead());
            Commit currentBranchCommitID = Commit.fromFile(currentbranchCommit);
            for (String key : currentBranchCommitID.fileList.keySet()) {
                if (!branchCommitID.fileList.containsKey(key)) {
                    File s = new File(CWD, key);
                    Utils.restrictedDelete(s);
                }
            }
            this.getStagingArea().getRemove().clear();
            this.getStagingArea().getAddition().clear();

        }
        return 0;


    }

    public void makeMergeCommit(Commit LCACommit, Commit currentCommit, Commit otherCommit, String currBranch, String mergeBranch) {

        HashMap<String, String> curr = currentCommit.fileList;
        HashMap<String, String> lca = LCACommit.fileList;
        HashMap<String, String> oth = otherCommit.fileList;
        boolean ifConflict = false;
//        System.out.println("这是LCA");
//        System.out.println(lca);
//        System.out.println("这是curr");
//        System.out.println(curr);
//        System.out.println("这是oth");
//        System.out.println(oth);
        Set<String> allFileNames = new HashSet<>(curr.keySet());
        allFileNames.addAll(oth.keySet());

        boolean errorfound = false;
        for (String fileName : oth.keySet()) {
            File s = new File(CWD, fileName);
            boolean first = lca.containsKey(fileName) && (!curr.containsKey(fileName));
            boolean second = (!lca.containsKey(fileName)) && !(curr.containsKey(fileName));
            if (first || second) {
                if (s.exists()) {
                    errorfound = true;
                }
            }
        }
        if (errorfound) {
            System.out.println("There is an untracked file in the way; delete it, "
                    + "or add and commit it first.");
            return;

        }

        for (String currFile : allFileNames) {
            String lcaHash = lca.getOrDefault(currFile, "");
            String currHash = curr.getOrDefault(currFile, "");
            String othHash = oth.getOrDefault(currFile, "");


            if (lca.get(currFile) != null && (curr.get(currFile) != null) && (oth.get(currFile) != null)) {

                //1 LCA:a ,LEFT:a  ,RIGHT:a1 :final:a1,write in cwd
                if (lcaHash.equals(currHash) && (!lcaHash.equals(othHash))) {
                    File s = new File(CWD, currFile);
                    Utils.writeContents(s, Blob.fromFile(oth.get(currFile)).getData());
                    this.add(currFile);
//                    System.out.println("1");

                } else if (lcaHash.equals(othHash) && (!lcaHash.equals(currHash))) {
                    //2 LCA:a,LEFT:a1  ,RIGHT:a:final:a1
//                    System.out.println("2");
                    continue;


                } else if (currHash.equals(othHash) && (!lcaHash.equals(currHash))) {
                    //3 LCA:a ,LEFT:a1  ,RIGHT:a1 final:a1
//                    System.out.println("3");
                    continue;

                } else if ((!lcaHash.equals(currHash)) && (!lcaHash.equals(othHash)) && (!othHash.equals(currHash))) {
                    //8.LCA:a ,LEFT:a1,RIGHT:a2, final:conflict  ,writeConflictFile
//                    System.out.println("8");
                    MergeUtils.writeConflictFile(currFile, curr.get(currFile), oth.get(currFile));
                    ifConflict = true;
                    this.add(currFile);

                } else {
                    assert false;
                }

            } else if (curr.get(currFile) == null && oth.get(currFile) != null) {
                if (lca.get(currFile) == null) {
                    //5 LCA:__ , LEFT:__ ,RIGHT:a final:a,write in cwd
//                    System.out.println("5");
                    File s = new File(CWD, currFile);
                    Utils.writeContents(s, Blob.fromFile(oth.get(currFile)).getData());
                    this.add(currFile);
//                    System.out.println("到底加没加");
//                    System.out.println(curr);

                } else {
                    //7  LCA:a ,LEFT:__  ,RIGHT:a, final:__
                    if (lcaHash.equals(othHash)) {
//                        System.out.println("7");
                        continue;
                    } else if (!lcaHash.equals(othHash)) {
                        //9LCA:a ,LEFT:__,RIGHT:a2, final:conflict  writeConflictFile
//                        System.out.println("9");
                        MergeUtils.writeConflictFile(currFile, curr.get(currFile), oth.get(currFile));
                        ifConflict = true;
                        this.add(currFile);
                    }
                }

            } else if (curr.get(currFile) != null && oth.get(currFile) == null) {
                if (lca.get(currFile) == null) {
                    //4 LCA:__ ,LEFT: a ,RIGHT: __ final:a
//                    System.out.println("4");
                    continue;
                } else {
                    //LCA.   a.  LEFT:a1,RIGHT:____, final:conflict  ,writeConflictFile
                    if (!lcaHash.equals(currHash)) {
                        MergeUtils.writeConflictFile(currFile, curr.get(currFile), oth.get(currFile));
                        ifConflict = true;
                        this.add(currFile);
                    }
                    //6  LCA:a ,LEFT:a  ,RIGHT__ final  __   ,write in cwd_delete
//                    System.out.println("6");
                    else {
                        this.remove(currFile);
                    }
                }

            } else if (lca.get(currFile) == null && (curr.get(currFile) != null
            ) && (oth.get(currFile) != null)) {
                //10,LCA:__ ,LEFT:a1,RIGHT:a2, final:conflict  ,writeConflictFile
//                System.out.println("10");
                MergeUtils.writeConflictFile(currFile, curr.get(currFile), oth.get(currFile));
                ifConflict = true;
                this.add(currFile);

            } else {
                assert false;
            }
        }

        if (ifConflict) {
            System.out.println("Encountered a merge conflict.");
        }
        String message = "Merged" + " " + mergeBranch + " " + "into" + " " + currBranch + ".";
        this.makeCommit(message, otherCommit.getCommitID());

    }

    //init master:f.txt(wug) g.txt(notwug)
    //branch master: add h.txt(wug2),rm g.txt,f.txt(wug2)/////f(wug2),h(wug2)
    //branch other: rm f.txt, add k.txt(wug3) ///////g.txt(not wug),k.txt(wug3)
    //branch master: merge other:
    //(f,h,g,k)
    //f:Lca:  a.txt  curr:   a1  oth: __
    //h:Lca: XX  curr: a     oth: xx
    //k:Lca: xx curr: xx   oth: a


}
